﻿/*!

aQuery.js Array Library
Copyright (C) 2014 Daniel Henry

Permission is hereby granted, free of charge, to any person obtaining a copy of this software
and associated documentation files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or
substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING
BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

*/

(function () {
    var aQuery = function (array) {
        /// <field name="array" type="Array">The array this aQuery object uses to execute its methods.</field>
        this.array = array;
    };

    aQuery.prototype.all = function (predicate) {
        /// <signature>
        ///     <summary>Determines whether all elements of the array satisfy a condition. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        var array = this.array;
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (typeof Array.prototype.every === 'function') {
            return Array.prototype.every.call(array, predicate);
        } else {
            for (var i = 0, ii = array.length; i < ii; ++i) {
                if (!predicate(array[i])) {
                    return false;
                }
            }
            return true;
        }
    };

    aQuery.prototype.any = function (predicate) {
        /// <signature>
        ///     <summary>Determines whether there are any elements in the array. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <returns type="Boolean" />
        /// </signature>
        /// <signature>
        ///     <summary>Determines whether any element of the array satisfies a condition. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        var array = this.array;
        if (!predicate) {
            return array.length > 0;
        } else if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (typeof Array.prototype.some === 'function') {
            return Array.prototype.some.call(array, predicate);
        } else {
            for (var i = 0, ii = array.length; i < ii; ++i) {
                if (predicate(array[i])) {
                    return true;
                }
            }
            return false;
        }
    };

    aQuery.prototype.average = function (selector) {
        /// <signature>
        ///     <summary>Computes the average of an array of numbers. If the array is not exclusively numbers, use another overload.</summary>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Computes the average of the array of numbers that are obtained by invoking a transform function on each element of the array.</summary>
        ///     <param name="selector">A transform function to apply to each element.</param>
        ///     <returns type="Number" />
        /// </signature>
        if (typeof selector !== 'function') {
            selector = function (e) {
                return e;
            };
        }
        return this.sum(selector) / this.array.length;
    };

    aQuery.prototype.cast = function (type) {
        /// <signature>
        ///     <summary>Converts the elements of the array to the specified type.</summary>
        ///     <param name="type">The type to cast the elements of the array to: Boolean -or- Number -or- String -or- 'boolean' -or- 'number' -or- 'string'</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (type === Boolean || type === 'boolean') {
            return this.select(function (e) {
                return !!e;
            });
        } else if (type === Number || type === 'number') {
            return this.select(function (e) {
                return (+e);
            });
        } else if (type === String || type === 'string') {
            return this.select(function (e) {
                return new String(e);
            });
        }
        throw new Error('type not recognized');
    };

    aQuery.prototype.concat = function (second) {
        /// <signature>
        ///     <summary>Concatenates to a second array onto the end of this array and returns the result. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="second">The second array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (typeof Array.prototype.concat === 'function') {
            return new aQuery(Array.prototype.concat.call(this.array, coerceToArray(second)));
        } else {
            var a = [];
            second = new aQuery(second);
            this.each(function (e) {
                a.push(e);
            });
            second.each(function (e) {
                a.push(e);
            });
            return new aQuery(a);
        }
    };

    aQuery.prototype.contains = function (value, comparer) {
        /// <signature>
        ///     <summary>Determines whether the array contains a specified element by using the JavaScript === operator.</summary>
        ///     <param name="value">The value to locate in the array.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        /// <signature>
        ///     <summary>Determines whether the array contains a specified element by using a specified comparison function.</summary>
        ///     <param name="value">The value to locate in the array.</param>
        ///     <param name="comparer">A function to compare values.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        return this.any(function (e) {
            return comparer(e, value);
        });
    };

    aQuery.prototype.count = function (predicate) {
        /// <signature>
        ///     <summary>Returns the number of elements in the array.</summary>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns a number that represents how many elements in the array satisfy a condition.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Number" />
        /// </signature>
        if (!predicate) {
            return this.array.length;
        } else if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        var result = 0;
        this.each(function (e) {
            if (predicate(e)) {
                ++result;
            }
        });
        return result;
    };

    aQuery.prototype.crossJoin = function (second, joiner) {
        /// <signature>
        ///     <summary>Merges the properties of the elements of two arrays in every possible combination.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Correlates the elements of two arrays in every possible combination using a supplied joiner function.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <param name="joiner">A function to create a result element from two matching elements.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (!joiner) {
            joiner = function (a, b) {
                var o = {};
                for (prop in a) {
                    if (Object.prototype.hasOwnProperty.call(a, prop)) {
                        o[prop] = a[prop];
                    }
                }
                for (prop in b) {
                    if (Object.prototype.hasOwnProperty.call(b, prop)) {
                        o[prop] = b[prop];
                    }
                }
                return o;
            }
        } else if (typeof joiner !== 'function') {
            throw new Error('joiner must be a function');
        }
        var a = [];
        var result = new aQuery(a);
        this.each(function (e) {
            second.each(function (f) {
                a.push(joiner(e, f));
            });
        });
        return result;
    };

    aQuery.prototype.distinct = function (comparer) {
        /// <signature>
        ///     <summary>Returns distinct elements from the array by using the JavaScript === operator.</summary>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns distinct elements from the array by using a specified comparer.</summary>
        ///     <param name="comparer">An function to compare values.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        var a = [];
        var result = new aQuery(a);
        this.each(function (e) {
            if (!result.any(function (f) { return comparer(e, f); })) {
                a.push(e);
            }
        });
        return result;
    };

    aQuery.prototype.each = function (operation) {
        /// <signature>
        ///     <summary>Performs an operation on each element in the array and returns this aQuery object. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="operation">A function that accepts a single parameter.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var array = this.array;
        if (typeof operation !== 'function') {
            throw new Error('operation must be a function');
        }
        if (typeof Array.prototype.forEach === 'function') {
            Array.prototype.forEach.call(array, operation);
        } else {
            for (var i = 0, ii = array.length; i < ii; i++) {
                operation(array[i]);
            }
        }
        return this;
    };

    aQuery.prototype.elementAt = function (index) {
        /// <signature>
        ///     <summary>Returns the element at a specified index in the array.</summary>
        ///     <param name="index">The index of the element to retrieve.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array;
        index = (+index);
        if (index < 0 || index >= array.length) {
            throw new Error('index is less than 0 or greater than or equal to the number of elements in the array');
        }
        return array[index];
    };

    aQuery.prototype.elementAtOrDefault = function (index, defaultValue) {
        /// <signature>
        ///     <summary>Returns the element at a specified index in the array or undefine if the index is out of range.</summary>
        ///     <param name="index">The index of the element to retrieve.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the element at a specified index in the array or a default value if the index is out of range.</summary>
        ///     <param name="index">The index of the element to retrieve.</param>
        ///     <param name="defaultValue">The value to return if the index is out of range.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array;
        index = (+index);
        if (index < 0 || index >= array.length) {
            return defaultValue;
        }
        return array[index];
    };

    aQuery.prototype.except = function (second, comparer) {
        /// <signature>
        ///     <summary>Produces the set difference of two arrays by using the JavaScript === operator.</summary>
        ///     <param name="second">An aray whose elements that also occur in the this array will cause those elements to be removed from the returned array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Produces the set difference of two arrays by using a comparer.</summary>
        ///     <param name="second">An aray whose elements that also occur in the this array will cause those elements to be removed from the returned array.</param>
        ///     <param name="comparer">A function to compare values.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        var result = [];
        this.each(function (e) {
            if (!second.contains(e, comparer)) {
                result.push(e);
            }
        });
        return new aQuery(result);
    };

    aQuery.prototype.first = function (predicate) {
        /// <signature>
        ///     <summary>Returns the first element in the array or throws an error if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the first element in the array that satisfies a specified condition or throws an error if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array;
        if (!predicate) {
            predicate = function (e) {
                return true;
            };
        } else if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (typeof Array.prototype.find === 'function') {
            var found = Array.prototype.find.call(array, predicate);
            if (typeof found === 'undefined') {
                throw new Error('no elements satisfied the condition');
            } else {
                return found;
            }
        } else {
            for (var i = 0, ii = array.length; i < ii; ++i) {
                var element = array[i];
                if (predicate(element)) {
                    return element;
                }
            }
            throw new Error('no elements satisfied the condition');
        }
    };

    aQuery.prototype.firstOrDefault = function (predicate, defaultValue) {
        /// <signature>
        ///     <summary>Returns the first element of the array or undefined if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the first element of the array that satisfies a condition or undefined if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the first element of the array or a default value if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="defaultValue">The value to return if the array is empty.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the first element of the array that satisfies a condition or a default value if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <param name="defaultValue">The value to return if no element is found that satisfies the condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array,
            usedPredicate = null,
            usedDefaultValue = null;
        for (var a = 0, aa = arguments.length; a < aa; ++a) {
            var arg = arguments[a];
            if (typeof arg === 'function' && !usedPredicate) {
                usedPredicate = arg;
            } else if (arg && !usedDefaultValue) {
                usedDefaultValue = arg;
            }
        }
        if (!usedPredicate) {
            usedPredicate = function (e) {
                return true;
            };
        }
        if (typeof Array.prototype.find === 'function') {
            var found = Array.prototype.find.call(array, usedPredicate);
            if (typeof found === 'undefined') {
                return defaultValue;
            } else {
                return found;
            }
        } else {
            for (var i = 0, ii = array.length; i < ii; ++i) {
                var element = array[i];
                if (usedPredicate(element)) {
                    return element;
                }
            }
            return usedDefaultValue;
        }
    };

    aQuery.prototype.groupBy = function (keySelector, comparer) {
        /// <signature>
        ///     <summary>Groups the elements of the array according to a specified key selector function and compares the keys by using the JavaScript === operator.</summary>
        ///     <param name="keySelector">A function to extract the key for each element.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Groups the elements of the array according to a specified key selector function and compares the keys by using a specified comparer.</summary>
        ///     <param name="keySelector">A function to extract the key for each element.</param>
        ///     <param name="comparer">A function to compare keys.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (typeof keySelector !== 'function') {
            throw new Error('keySelector must be a function');
        }
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        var a = [];
        var q = new aQuery(a);
        this.each(function (e) {
            var key = keySelector(e);
            var group = q.firstOrDefault(function (g) {
                return comparer(g.key, key);
            });
            if (!group) {
                group = {
                    key: key,
                    values: []
                };
                a.push(group);
            }
            group.values.push(e);
        });
        return q.select(function (e) {
            return {
                key: e.key,
                values: new aQuery(e.values)
            };
        });
    };

    aQuery.prototype.indexOf = function (predicate) {
        /// <signature>
        ///     <summary>Returns the index of the first element of the array equal to a specified value or -1 if no such element is found. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="value">The value for which to search the array.</param>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the index of the first element of the array that satisfies a condition or -1 if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Number" />
        /// </signature>
        var array = this.array;
        if (typeof predicate !== 'function') {
            if (typeof Array.prototype.indexOf === 'function') {
                return Array.prototype.indexOf.call(array, predicate);
            } else {
                var value = predicate;
                predicate = function (e) {
                    return e === value;
                };
            }
        }
        if (typeof Array.prototype.findIndex === 'function') {
            return Array.prototype.findIndex.call(array, predicate);
        } else {
            for (var i = 0, ii = array.length; i < ii; ++i) {
                if (predicate(array[i])) {
                    return i;
                }
            }
            return -1;
        }
    };

    aQuery.prototype.intersect = function (second, comparer) {
        /// <signature>
        ///     <summary>Produces the set intersection of two arrays by using the JavaScript === operator.</summary>
        ///     <param name="second">An array whose distinct elements that also appear in the first array will be returned.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Produces the set intersection of two arrays by using a comparer.</summary>
        ///     <param name="second">An array whose distinct elements that also appear in the first array will be returned.</param>
        ///     <param name="comparer">A function to compare values.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        var result = [];
        this.distinct(comparer).each(function (e) {
            if (second.contains(e, comparer)) {
                result.push(e);
            }
        });
        return new aQuery(result);
    };

    aQuery.prototype.join = function (second, predicate, joiner) {
        /// <signature>
        ///     <summary>Correlates the elements of two arrays based on matching keys. A specified function is used to compare keys. The properties of matched elements are merged together in the joined elements.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <param name="predicate">A function that determines whether an element from this array matches an element of the second array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Correlates the elements of two arrays based on matching keys. A specified function is used to compare keys. Matched elements are merged together by a joiner function.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <param name="predicate">A function that determines whether an element from this array matches an element of the second array.</param>
        ///     <param name="joiner">A function to create a result element from two matching elements.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (!joiner) {
            joiner = function (a, b) {
                var o = {};
                for (prop in a) {
                    if (Object.prototype.hasOwnProperty.call(a, prop)) {
                        o[prop] = a[prop];
                    }
                }
                for (prop in b) {
                    if (Object.prototype.hasOwnProperty.call(b, prop)) {
                        o[prop] = b[prop];
                    }
                }
                return o;
            };
        } else if (typeof joiner !== 'function') {
            throw new Error('joiner must be a function');
        }
        var a = [];
        var result = new aQuery(a);
        this.each(function (e) {
            second
                .where(function (f) {
                    return predicate(e, f);
                })
                .each(function (f) {
                    a.push(joiner(e, f));
                });
        });
        return result;
    };

    aQuery.prototype.last = function (predicate) {
        /// <signature>
        ///     <summary>Returns the last element of the array or throws an error if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the last element of the array that satisfies a specified condition or throws an error if no such element is found.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array;
        if (!predicate) {
            predicate = function (e) {
                return true;
            };
        } else if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (typeof Array.prototype.find === 'function') {
            var copy = Array.prototype.slice.call(array);
            Array.prototype.reverse.call(copy);
            var found = Array.prototype.find.call(copy, predicate);
            if (typeof found === 'undefined') {
                throw new Error('no elements satisfied the condition');
            } else {
                return found;
            }
        }
        else {
            for (var i = array.length - 1; i >= 0; --i) {
                var element = array[i];
                if (predicate(element)) {
                    return element;
                }
            }
            throw new Error('no elements satisfied the condition');
        }
    };

    aQuery.prototype.lastIndexOf = function (predicate) {
        /// <signature>
        ///     <summary>Returns the index of the last element of the array equal to a specified value or -1 if no such element is found. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="value">The value for which to search the array.</param>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the index of the last element of the array that satisfies a condition or -1 if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Number" />
        /// </signature>
        var array = this.array;
        if (typeof predicate !== 'function') {
            if (typeof Array.prototype.lastIndexOf === 'function') {
                return Array.prototype.lastIndexOf.call(array, predicate);
            } else {
                var value = predicate;
                predicate = function (e) {
                    e === value;
                };
            }
        }
        if (typeof Array.prototype.findIndex === 'function') {
            var copy = Array.prototype.slice.call(array);
            Array.prototype.reverse.call(copy);
            var foundIndex = Array.prototype.foundIndex.call(copy, predicate);
            if (foundIndex > -1) {
                foundIndex = copy.length - foundIndex;
            }
            return foundIndex;
        }
        else {
            for (var i = array.length - 1; i >= 0; --i) {
                if (predicate(array[i])) {
                    return i;
                }
            }
            return -1;
        }
    };

    aQuery.prototype.lastOrDefault = function (predicate, defaultValue) {
        /// <signature>
        ///     <summary>Returns the last element of the array or undefined if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the last element of the array that satisfies a condition or undefined if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the last element of the array or a default value if the array is empty. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="defaultValue">The value to return if the array is empty.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the last element of the array that satisfies a condition or a default value if no such element is found. Accelerated by native code in ECMAScript 6 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <param name="defaultValue">The value to return if no element is found that satisfies the condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array,
            usedPredicate = null,
            usedDefaultValue = null;
        for (var a = 0, aa = arguments.length; a < aa; ++a) {
            var arg = arguments[a];
            if (typeof arg === 'function' && !usedPredicate) {
                usedPredicate = arg;
            } else if (arg && !usedDefaultValue) {
                usedDefaultValue = arg;
            }
        }
        if (!usedPredicate) {
            usedPredicate = function (e) {
                return true;
            };
        }
        if (typeof Array.prototype.find === 'function') {
            var copy = Array.prototype.slice.call(array);
            Array.prototype.reverse.call(copy);
            var found = Array.prototype.find.call(copy, usedPredicate);
            if (typeof found === 'undefined') {
                return defaultValue;
            } else {
                return found;
            }
        } else {
            for (var i = array.length - 1; i >= 0; --i) {
                var element = array[i];
                if (usedPredicate(element)) {
                    return element;
                }
            }
            return usedDefaultValue;
        }
    };

    aQuery.prototype.max = function (selector) {
        /// <signature>
        ///     <summary>Returns the element of the array with the greatest value.</summary>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Invokes a transform function on each element of the array and returns the maximum value.</summary>
        ///     <param name="selector">A transform function to apply to each element.</param>
        ///     <returns type="Number" />
        /// </signature>
        if (!selector) {
            selector = function (e) {
                return e;
            };
        } else if (typeof selector !== 'function') {
            throw new Error('selector must be a function');
        }
        if (!this.array.length) {
            throw new Error('array contains no elements');
        }
        var maxValue = null;
        this.each(function (e) {
            var value = selector(e);
            if (value > maxValue || !maxValue) {
                maxValue = value;
            }
        });
        return maxValue;
    };

    aQuery.prototype.min = function (selector) {
        /// <signature>
        ///     <summary>Returns the element of the array with the least value.</summary>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Invokes a transform function on each element of the array and returns the minimum value.</summary>
        ///     <param name="selector">A transform function to apply to each element.</param>
        ///     <returns type="Number" />
        /// </signature>
        if (!selector) {
            selector = function (e) {
                return e;
            };
        } else if (typeof selector !== 'function') {
            throw new Error('selector must be a function');
        }
        if (!this.array.length) {
            throw new Error('array contains no elements');
        }
        var minValue = null;
        this.each(function (e) {
            var value = selector(e);
            if (value < minValue || !minValue) {
                minValue = value;
            }
        });
        return minValue;
    };

    aQuery.prototype.ofType = function (type) {
        /// <signature>
        ///     <summary>Filters the elements of the array based on a specified type.</summary>
        ///     <param name="type">The type to filter the elements of the array on.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (typeof type === 'function' && type !== Boolean && type !== Object && type !== Number && type !== String) {
            return this.where(function (e) {
                return e instanceof type;
            });
        } else if (typeof type === 'string') {
            return this.where(function (e) {
                return typeof e === type ||
                    (type === 'boolean' && e instanceof Boolean) ||
                    (type === 'null' && e === null) ||
                    (type === 'object' && e instanceof Object) ||
                    (type === 'number' && e instanceof Number) ||
                    (type === 'string' && e instanceof String) ||
                    (type === 'undefined' && e === undefined);
            });
        } else {
            return this.where(function (e) {
                return (type !== undefined && type !== null && e instanceof type) ||
                    (type === Boolean && typeof e === 'boolean') ||
                    (type === null && e === null) ||
                    (type === Object && typeof e === 'object') ||
                    (type === Number && typeof e === 'number') ||
                    (type === String && typeof e === 'string') ||
                    (type === undefined && e === undefined);
            });
        }
    };

    aQuery.prototype.orderBy = function (keySelectors) {
        /// <signature>
        ///     <summary>Sorts the elements of the array in ascending order.</summary>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Sorts the elements of the array in according to a key.</summary>
        ///     <param name="keySelectors">A function or sequence of functions to extract a key from an element.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (!keySelectors) {
            keySelectors = [{
                a: function (e) {
                    return e;
                }
            }];
        } else if (typeof keySelectors === 'function') {
            return this.orderBy([{
                a: keySelectors
            }]);
        } else if (keySelectors instanceof aQuery) {
            keySelectors = keySelectors.array;
        } else if (!(keySelectors instanceof Array)) {
            throw new Error('keySelectors must be a function or an array');
        }
        for (var i = 0, ii = keySelectors.length; i < ii; ++i) {
            var keySelector = keySelectors[i];
            if (typeof keySelector.a !== 'function' && typeof keySelector.d !== 'function') {
                throw new Error('keySelector at index ' + i + ' does not define either an ascending (a) or descending (d) function');
            } else if (typeof keySelector.a === 'function' && typeof keySelector.d === 'function') {
                throw new Error('keySelector at index ' + i + ' defines both an ascending (a) and descending (d) function, which is illegal');
            }
        }
        var result = Array.prototype.slice.call(this.array);
        Array.prototype.sort.call(result, function (a, b) {
            var compare = function (keySelector) {
                var keySelectorFunction = null,
                    descending = false;
                if (keySelector.a) {
                    keySelectorFunction = keySelector.a;
                } else {
                    keySelectorFunction = keySelector.d;
                    descending = true;
                }
                var aVal = keySelectorFunction(a);
                var bVal = keySelectorFunction(b);
                if (aVal < bVal) {
                    return descending ? 1 : -1;
                } else if (aVal > bVal) {
                    return descending ? -1 : 1;
                }
                return 0;
            };
            var comparisonResult = 0;
            for (var i = 0, ii = keySelectors.length; i < ii; ++i) {
                comparisonResult = compare(keySelectors[i]);
                if (comparisonResult) {
                    break;
                }
            }
            return comparisonResult;
        });
        return new aQuery(result);
    };

    aQuery.prototype.orderByDescending = function (keySelector) {
        /// <signature>
        ///     <summary>Sorts the elements of the array in descending order.</summary>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Sorts the elements of the array in descending order according to a key.</summary>
        ///     <param name="keySelector">A function to extract a key from an element.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (!keySelector) {
            keySelector = function (e) {
                return e;
            };
        } else if (typeof keySelector !== 'function') {
            throw new Error('keySelector must be a function');
        }
        return this.orderBy([{
            d: keySelector
        }]);
    };

    aQuery.prototype.outerJoin = function (second, predicate, joiner) {
        /// <signature>
        ///     <summary>Correlates the elements of two arrays based on matching keys. A specified function is used to compare keys. Also includes un-matched elements. The properties of matched elements are merged together in the joined elements.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <param name="predicate">A function that determines whether an element from this array matches an element of the second array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Correlates the elements of two arrays based on matching keys. A specified function is used to compare keys. Also includes un-matched elements. Matched elements are merged together by a joiner function.</summary>
        ///     <param name="second">The array to join to the first array.</param>
        ///     <param name="predicate">A function that determines whether an element from this array matches an element of the second array.</param>
        ///     <param name="joiner">A function to create a result element from two matching elements.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (!joiner) {
            joiner = function (a, b) {
                var o = {};
                for (prop in a) {
                    if (Object.prototype.hasOwnProperty.call(a, prop)) {
                        o[prop] = a[prop];
                    }
                }
                for (prop in b) {
                    if (Object.prototype.hasOwnProperty.call(b, prop)) {
                        o[prop] = b[prop];
                    }
                }
                return o;
            };
        }
        if (typeof joiner !== 'function') {
            throw new Error('joiner must be a function');
        }
        var a = [];
        var result = new aQuery(a);
        this.each(function (e) {
            second
                .where(function (f) {
                    return predicate(e, f);
                })
                .each(function (f) {
                    a.push(joiner(e, f));
                });
        });
        var that = this;
        this
            .where(function (e) {
                return !second.any(function (f) {
                    return predicate(e, f);
                });
            })
            .each(function (e) {
                a.push(joiner(e, null));
            });
        second
            .where(function (f) {
                return !that.any(function (e) {
                    return predicate(e, f);
                });
            })
            .each(function (f) {
                a.push(joiner(null, f));
            });
        return result;
    };

    aQuery.prototype.select = function (selector) {
        /// <signature>
        ///     <summary>Projects each element of the array into a new form. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="selector">A transform function to apply to each element.</param>
        ///     <returns type="aQuery" />
        /// <signature>
        if (typeof selector !== 'function') {
            throw new Error('selector must be a function');
        }
        if (typeof Array.prototype.map === 'function') {
            return new aQuery(Array.prototype.map.call(this.array, selector));
        } else {
            var result = [];
            this.each(function (e) {
                result.push(selector(e));
            });
            return new aQuery(result);
        }
    };

    aQuery.prototype.sequenceEqual = function (second, comparer) {
        /// <signature>
        ///     <summary>Determines whether two arrays are equal by comparing their elements by using the JavaScript === operator.</summary>
        ///     <param name="second">An array to compare to the first sequence.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        /// <signature>
        ///     <summary>Determines whether two arrays are equal by comparing their elements by using a specified comparer.</summary>
        ///     <param name="second">An array to compare to the first sequence.</param>
        ///     <param name="comparer">An function to use to compare elements.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        var array = this.array;
        second = new aQuery(second);
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        if (array.length !== second.count()) {
            return false;
        }
        for (var i = 0, ii = array.length; i < ii; ++i) {
            if (!comparer(array[i], second.elementAt(i))) {
                return false;
            }
        }
        return true;
    };

    aQuery.prototype.single = function (predicate) {
        /// <signature>
        ///     <summary>Returns the only element of the array or throws an error if more one element exists.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the only element of the array that satisfies a specified condition or throws an error if more than one such element exists.</summary>
        ///     <param name="predicate">A function to test an element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array;
        if (!predicate) {
            predicate = function (e) { return true; };
        } else if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        var match = null,
            matchesFound = 0;
        for (var i = 0, ii = array.length; i < ii; i++) {
            var e = array[i];
            if (predicate(e)) {
                match = e;
                if (++matchesFound > 1)
                    throw new Error('more than one element satisfied the condition');
            }
        }
        if (matchesFound == 0) {
            throw new Error('no elements satisfied the condition');
        }
        return match;
    };

    aQuery.prototype.singleOrDefault = function (predicate, defaultValue) {
        /// <signature>
        ///     <summary>Returns the only element of the array or undefined if the array is empty, or throws an error if there is more than one element.</summary>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the only element of the array that satisfies a specified condition or undefined if no such element exists, or throws an error if more than one element satisfies the condition.</summary>
        ///     <param name="predicate">A function to test an element for a condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the only element of the array or a default value if the array is empty, or throws an error if there is more than one element.</summary>
        ///     <param name="defaultValue">The value to return if the array is empty.</param>
        ///     <returns type="Object" />
        /// </signature>
        /// <signature>
        ///     <summary>Returns the only element of the array that satisfies a specified condition or a default value if no such element exists, or throws an error if more than one element satisfies the condition.</summary>
        ///     <param name="predicate">A function to test an element for a condition.</param>
        ///     <param name="defaultValue">The value to return if no element is found that satisfies the condition.</param>
        ///     <returns type="Object" />
        /// </signature>
        var array = this.array,
            usedPredicate = null,
            usedDefaultValue = null;
        for (var a = 0, aa = arguments.length; a < aa; ++a) {
            var arg = arguments[a];
            if (typeof arg === 'function' && !usedPredicate) {
                usedPredicate = arg;
            } else if (arg && !usedDefaultValue) {
                usedDefaultValue = arg;
            }
        }
        if (!usedPredicate) {
            usedPredicate = function (e) {
                return true;
            };
        }
        var match = null,
            matchesFound = 0;
        for (var i = 0, ii = array.length; i < ii; i++) {
            var e = array[i];
            if (usedPredicate(e)) {
                match = e;
                if (++matchesFound > 1) {
                    throw new Error('more than one element satisfied the condition');
                }
            }
        }
        if (matchesFound == 0) {
            return usedDefaultValue;
        }
        return match;
    };

    aQuery.prototype.skip = function (count) {
        /// <signature>
        ///     <summary>Bypasses a specified number of elements in the array and then returns the remaining elements.</summary>
        ///     <param name="count">The number of elements to skip before returning the remaining elements.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var array = this.array,
            result = [];
        for (var i = (+count), ii = array.length; i < ii; ++i) {
            result.push(array[i]);
        }
        return new aQuery(result);
    };

    aQuery.prototype.skipWhile = function (predicate) {
        /// <signature>
        ///     <summary>Bypasses elements in the array as long as a specified condition is true and then returns the remaining elements.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var array = this.array;
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        var result = [];
        var i = 0,
            ii = array.length;
        while (i < ii) {
            if (!predicate(array[i])) {
                break;
            }
            ++i;
        }
        while (i < ii) {
            result.push(array[i]);
            ++i;
        }
        return new aQuery(result);
    };

    aQuery.prototype.sum = function (selector) {
        /// <signature>
        ///     <summary>Computes the sum of an array of numbers. If the array is not exclusively numbers, use another overload.</summary>
        ///     <returns type="Number" />
        /// </signature>
        /// <signature>
        ///     <summary>Computes the sum of the array of numbers that are obtained by invoking a transform function on each element of the array.</summary>
        ///     <param name="selector">The transform function to apply to each element.</param>
        ///     <returns type="Number" />
        /// </signature>
        if (!selector) {
            selection = function (e) {
                return e;
            };
        } else if (typeof selector !== 'function') {
            throw new Error('selector must be a function');
        }
        var result = 0;
        this.each(function (e) {
            result += selector(e);
        });
        return result;
    };

    aQuery.prototype.take = function (count) {
        /// <signature>
        ///     <summary>Returns a specified number of contiguous elements from the start of the array.</summary>
        ///     <param name="count">The number of elements to return.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var array = this.array,
            result = [];
        for (var i = 0, ii = count < array.length ? count : array.length; i < ii; ++i) {
            result.push(array[i]);
        }
        return new aQuery(result);
    };

    aQuery.prototype.takeWhile = function (predicate) {
        /// <signature>
        ///     <summary>Returns elements from an array as long as a specified condition is true.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var array = this.array;
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        var result = [];
        for (var i = 0, ii = array.length; i < ii; ++i) {
            var element = array[i];
            if (!predicate(element)) {
                break;
            }
            result.push(element);
        }
        return new aQuery(result);
    };

    aQuery.prototype.toArray = function () {
        /// <signature>
        ///     <summary>Returns a shallow copy of the underlying array. If you want to access the underlying array directly, use the .array property.</summary>
        ///     <returns type="Array" />
        /// </signature>
        return Array.prototype.slice.call(this.array);
    };

    aQuery.prototype.toString = function () {
        /// <signature>
        ///     <summary>Returns the result of invoking the array's toString method.</summary>
        ///     <returns type="String" />
        /// </signature>
        return this.array.toString();
    };

    aQuery.prototype.union = function (second, comparer) {
        /// <signature>
        ///     <summary>Produces the set union of two arrays by using the JavaScript === operator.</summary>
        ///     <param name="second">An array whose distinct elements form the second set for the union.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Produces the set union of two arrays by using a specified comparer.</summary>
        ///     <param name="second">An array whose distinct elements form the second set for the union.</param>
        ///     <param name="comparer">A function to compare values.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        second = new aQuery(second);
        if (!comparer) {
            comparer = function (a, b) {
                return a === b;
            };
        } else if (typeof comparer !== 'function') {
            throw new Error('comparer must be a function');
        }
        var a = this.distinct().array;
        var result = new aQuery(a);
        second.each(function (e) {
            if (!result.contains(e)) {
                a.push(e);
            }
        });
        return result;
    };

    aQuery.prototype.where = function (predicate) {
        /// <signature>
        ///     <summary>Filters the array of values based on a predicate. Accelerated by native code in ECMAScript 5.1 or greater.</summary>
        ///     <param name="predicate">A function to test each element for a condition.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (typeof predicate !== 'function') {
            throw new Error('predicate must be a function');
        }
        if (typeof Array.prototype.filter) {
            return new aQuery(Array.prototype.filter.call(this.array, predicate));
        } else {
            var result = [];
            this.each(function (e) {
                if (predicate(e)) {
                    result.push(e);
                }
            });
            return new aQuery(result);
        }
    };

    var coerceToArray = function (o) {
        if (o instanceof Array) {
            return o;
        } else if (o instanceof aQuery) {
            return o.array;
        } else if (o && typeof o.toArray === 'function') {
            return o.toArray();
        } else if (o) {
            return Array.prototype.slice.call(o);
        } else {
            return [];
        }
    };

    var AQ = function create(o) {
        /// <signature>
        ///     <summary>Create a new aQuery object.</summary>
        ///     <param name="o">An array.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Create a new aQuery object (not really, I'm just gonna return what you pass to me right back).</summary>
        ///     <param name="o">An aQuery object.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Create a new aQuery object.</summary>
        ///     <param name="o">An object with a defined toArray method.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        /// <signature>
        ///     <summary>Create a new aQuery object.</summary>
        ///     <param name="o">An "array-like" object (e.g. arguments).</param>
        ///     <returns type="aQuery" />
        /// </signature>
        if (o instanceof aQuery) {
            return o;
        } else {
            return new aQuery(coerceToArray(o));
        }
    };

    AQ.isQuery = function (o) {
        /// <signature>
        ///     <summary>Determines if an object is an aQuery object.</summary>
        ///     <param name="o">The object to test.</param>
        ///     <returns type="Boolean" />
        /// </signature>
        return o instanceof aQuery;
    };

    AQ.range = function (start, count) {
        /// <signature>
        ///     <summary>Generates an array of integral numbers within a specified range.</summary>
        ///     <param name="start">The value of the first integer in the array.</param>
        ///     <param name="count">The number of sequential integers to generate.</param>
        ///     <returns type="aQuery" />
        /// </signature>
        var result = [];
        for (var i = start, ii = start + count; i <= ii; ++i) {
            result.push(i);
        }
        return new aQuery(result);
    };

    if (!Array.prototype.toQuery) {
        Array.prototype.toQuery = function () {
            /// <signature>
            ///     <summary>Converts this array into an aQuery object.</summary>
            ///     <returns type="aQuery" />
            /// </signature>
            return new aQuery(this);
        };
    }
    if (typeof module !== 'undefined' && module.exports) {
        module.exports = AQ;
    }
    else if (typeof window !== 'undefined') {
        window.AQ = AQ;
        if (jQuery !== 'undefined' && !jQuery().aq) {
            (function ($) {
                $.fn.aq = function () {
                    return AQ(this.toArray());
                };
                $.aq = function (o) {
                    return AQ(o);
                };
            })(jQuery);
        }
    }
}());